﻿namespace RialternativeSample.Client.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>
    /// The ViewModel and Model common logic.
    /// </summary>
    public abstract class Notifier : INotifyPropertyChanged, INotifyDataErrorInfo
    {
        /// <summary>
        /// Dictionary of the errors by the concerned property.
        /// </summary>
        private Dictionary<string, List<string>> currentErrors = new Dictionary<string, List<string>>();

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when the validation errors have changed for a property or for the entire object.
        /// </summary>
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>
        /// Gets a value indicating whether the object has validation errors.
        /// </summary>
        /// <value>The value.</value>
        /// <returns>true if the object currently has validation errors; otherwise, false.</returns>
        public bool HasErrors
        {
            get
            {
                return this.currentErrors.Any();
            }
        }

        /// <summary>
        /// Gets the validation errors for a specified property or for the entire object.
        /// </summary>
        /// <param name="propertyName">The name of the property to retrieve validation errors for, or null or <see cref="F:System.String.Empty"/> to retrieve errors for the entire object.</param>
        /// <returns>
        /// The validation errors for the property or object.
        /// </returns>
        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            if (!this.currentErrors.ContainsKey(propertyName))
            {
                return this.currentErrors.Values;
            }

            return this.currentErrors[propertyName];
        }

        /// <summary>
        /// Manages the errors.
        /// </summary>
        /// <typeparam name="T">The type of the property accessor.</typeparam>
        /// <param name="propAccess">The prop access.</param>
        /// <param name="errors">The errors.</param>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The generic type is needed for the method logic."), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Not applicable."), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Usefull for the error management.")]
        public void ManageErrors<T>(Expression<Func<T>> propAccess, List<string> errors)
        {
            bool raiseEventErrorsChanged = false;
            string property = GetPropertyName(propAccess);

            if (property == null)
            {
                return;
            }

            if (errors == null)
            {
                if (this.currentErrors.ContainsKey(property))
                {
                    this.currentErrors.Remove(property);
                    raiseEventErrorsChanged = true;
                }
            }
            else
            {
                if (!this.currentErrors.ContainsKey(property))
                {
                    this.currentErrors.Add(property, errors);
                    raiseEventErrorsChanged = true;
                }
            }

            if (raiseEventErrorsChanged && this.ErrorsChanged != null)
            {
                this.ErrorsChanged(this, new DataErrorsChangedEventArgs(property));
            }
        }

        /// <summary>
        /// Notify the interface that the property value has been changed.
        /// </summary>
        /// <typeparam name="T">The property type.</typeparam>
        /// <param name="propAccess">The prop access.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "RaisePropertyChanged wraps an event raising")]
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The generic type is needed for the method logic.")]
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Specific type is needed for the method logic.")]
        protected void RaisePropertyChanged<T>(Expression<Func<T>> propAccess)
        {
            string propertyName = GetPropertyName(propAccess);
            if (this.PropertyChanged != null && propertyName != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Validates the property.
        /// </summary>
        /// <typeparam name="T">The type of the property accessor.</typeparam>
        /// <param name="propAccess">The property accessor.</param>
        /// <param name="value">The value.</param>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The generic type is needed for the method logic.")]
        protected void ValidateProperty<T>(Expression<Func<T>> propAccess, string value)
        {
            Validator.ValidateProperty(
                    value,
                    new ValidationContext(this, null, null) { MemberName = GetPropertyName(propAccess) });
        }

        /// <summary>
        /// Gets the name of the property.
        /// </summary>
        /// <typeparam name="T">The type of the property accessor.</typeparam>
        /// <param name="propAccess">The prop access.</param>
        /// <returns>The name of the property.</returns>
        private static string GetPropertyName<T>(Expression<Func<T>> propAccess)
        {
            if (propAccess != null)
            {
                MemberExpression asMember = propAccess.Body as MemberExpression;
                if (asMember != null)
                {
                    return asMember.Member.Name;
                }
            }

            return null;
        }
    }
}
